#include "Socket.h"

using namespace TTP;
using namespace std;

void Socket::sock_init() {
   if(-1 == (sock = Socket(AF_INET6, SOCK_DGRAM, 0))) {
      throw msg_exception(strerror(errno));
   }
   
   int yes=1;
   if(-1 == setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int))) {
      close(sock);
      sock = -1;
      throw msg_exception(strerror(errno));
   }  

   sockaddr_in6 addr6; 
   in6_addr any = IN6ADDR_ANY_INIT;

   bzero (&addr6, sizeof (addr6));
   addr6.sin6_family = AF_INET6;
   addr6.sin6_port = htons(7008);
   addr6.sin6_addr = any;

   if(-1 == bind(sock, (sockaddr*)&addr6, sizeof(addr6))) {
      close(sock);
      sock = -1;
      throw msg_exception(strerror(errno));
   }
}


void Socket::_disconnect() {
   if(sock >= 0 && connected) {
      mreq.ipv6mr_interface = 0;
      memcpy(&mreq.ipv6mr_multiaddr.__u6_addr, &addr[0], addr.size());

      if(-1 == setsockopt(sock, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &mreq, sizeof(mreq))) {
         // FIXME: connected = false?
         throw msg_exception(strerror(errno));
      }

      addr.clear();
      connected = false;
   }
}

void Socket::_recv(vector<byte>& data) {
   data.clear();

   sockaddr_in6 addr6; 
   socklen_t len;

   ssize_t siz = recvfrom(sock, &data[0], 0, MSG_PEEK, &addr6, &len);

   if(siz == 0) {
      throw msg_exception("Got 0 bytes in recv");
   } 

   data.resize(siz);

   siz = recvfrom(sock, &data[0], data.size(), 0, &addr6, &len);
   Address ra.assign(&addr6.sin6_addr.__u6_addr, &addr6.sin6_addr.__u6_addr+len);

   if(siz == 0) {
      throw msg_exception("Got 0 bytes in second recv");
   }  else if (siz == -1) {
      throw msg_exception(strerror(errno));
   }
   
   if (addr6.sin6_port != htons(7008)) {
      throw msg_exception("Got message on unexpected port");
   } else if (addr != ra) {
      throw msg_exception("Got message from unexpected address");
   }
}


void Socket::connect(const Address &a) {
   assert(a.size() == 16);
   assert(a[0] == 0xff); // is multicastic!
   struct ipv6_mreq mreq;


   if(sock < 0) {
      throw msg_execption("Trying to connect with unbound Socket");
   } 

   _disconnect();

   mreq.ipv6mr_interface = 0;
   memcpy(&mreq.ipv6mr_multiaddr.__u6_addr, &a[0], a.size());

   if(-1 == setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq))) {
      throw msg_exception(strerror(errno));
   }

   addr = a; // copy this address as our new address
   connected = true;
}


void Socket::send(const vector<byte> & data) {
   if(sock < 0 || !connected) throw msg_exception("Sending while not connected");
   int siz = sendto(sock, &data[0], data.size(), 0, &addr[0], addr.size());
   if(siz == -1) {
      throw msg_exception(strerror(errno));
   } else if ( siz != data.size()) {
      throw msg_exception("Sent less data than I was expecting to!");
   }
}

void Socket::select(vector<byte>& data) {
   if(sock < 0) throw sock_exception("Selecting from dead Socket");

   fd_set fs;
   int num;

   for(;;) {
      FD_ZERO(&fs);
      FD_SET(sock, &fs);

      if((num = select(sock+1, &fs, 0, 0, 0)) == 1 and FD_ISSET(sock, &fd)) {
         try {
            _recv(data);
            return;
         } catch (msg_exception & e) {
            cerr << "In Select: " << e.what() << endl;
         }
      } else if(num == -1 && errno == EINTR) {
         throw EINTR_exception("select");
      } else {
         throw msg_exception(strerror(errno));
      }
   }
}
